using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using Eclipse.TRCore.Delegates;
using Microsoft.Win32;

namespace Eclipse.TRCore.Http
{

    public class IEClient : IHttpCapability, IHttpMethod, IhttpSpecialMethod
    {
        private CookieCollection _cookies = new CookieCollection();
        private WebHeaderCollection _clientHeads = new WebHeaderCollection();
        private WebHeaderCollection _serverHeads = new WebHeaderCollection();
        private volatile bool _isBusy = false;
        private bool _autoEncoding = true;
        private volatile Exception _lastException = null;
        private ICredentials _credentials;
        private Encoding _encoding = Encoding.Default;
        private IWebProxy _proxy;
        private int? _readWriteTimeout;
        private int? _timeout;
        private string _referer;

        const int DefaultCopyBufferLength = 8192;
        const int DefaultDownloadBufferLength = 65536;
        const string DefaultUploadFileContentType = "application/octet-stream";
        const string UploadFileContentType = "multipart/form-data";
        const string UploadValuesContentType = "application/x-www-form-urlencoded";
        const string DefaultBoundaryPrefix = "---------------------------7d"; //IE
        const string DefaultContentDisposition = "Content-Disposition: form-data; name=";
        const string DefaultMultipartContentType = "multipart/form-data; boundary=";
        const string DefaultMultipartFileContentType = "application/unknown";
        const string DefaultMixContentDisposition = "Content-disposition: attachment; filename=";
        const string DefaultMixedContentType = "Content-type: multipart/mixed, boundary=";


        private HttpWebRequest _webRequest;
        private HttpWebResponse _webResponse;
        #region IHttpCapability Members

        public CookieCollection Cookies
        {
            get { return _cookies; }
        }

        public WebHeaderCollection ClientHeads
        {
            get { return _clientHeads; }
        }

        public WebHeaderCollection ServerHeads
        {
            get { return _serverHeads; }
        }

        public bool IsBusy
        {
            get { return _isBusy; }
        }

        public Exception LastException
        {
            get { return _lastException; }
        }

        public ICredentials Credentials
        {
            get { return _credentials; }
            set { _credentials = value; }
        }

        public Encoding Encoding
        {
            get { return _encoding; }
            set { _encoding = value; }
        }

        public IWebProxy Proxy
        {
            get { return _proxy; }
            set { _proxy = value; }
        }

        public int? ReadWriteTimeout
        {
            get { return _readWriteTimeout; }
            set { _readWriteTimeout = value; }
        }

        public int? Timeout
        {
            get { return _timeout; }
            set { _timeout = value; }
        }

        public string Referer
        {
            get { return _referer; }
            set { _referer = value; }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public byte[] GetResponseBytes(Uri uri, HttpPostArguments httpPostArguments,
                                       OnGetRequestStream GetRequestStreamHandler,
                                       OnGetResponseStream GetResponseStreamHandler, bool throwException)
        {
            _isBusy = true;
            _webResponse = null;
            _webRequest = (HttpWebRequest)WebRequest.Create(uri);
            _webRequest.Proxy = Proxy == null ? WebRequest.DefaultWebProxy : Proxy;
            _webRequest.Credentials = Credentials;
            _webRequest.ReadWriteTimeout = ReadWriteTimeout.GetValueOrDefault(_webRequest.ReadWriteTimeout);
            _webRequest.Timeout = Timeout.GetValueOrDefault(_webRequest.Timeout);
            _webRequest.Method = httpPostArguments == null ? "GET" : "POST";
            _webRequest.Referer = Referer;
            _webRequest.CookieContainer = new CookieContainer();
            _webRequest.CookieContainer.Add(this.Cookies);
            byte[] bytes = null;
            try
            {
                _webResponse = httpPostArguments == null
                   ? GetResponseByGet()
                   : GetResponseByPost(httpPostArguments, GetRequestStreamHandler);
                _serverHeads = _webResponse.Headers;
                bytes = GetResponseBytes(GetResponseStreamHandler);

            }
            catch (Exception ex)
            {
                if (throwException) throw;
                else if(!(ex is ThreadAbortException)) _lastException = ex;
            }
            _isBusy = false;
            return bytes;
        }

        private HttpWebResponse GetResponseByGet()
        {
            return (HttpWebResponse)_webRequest.GetResponse();
        }

        private HttpWebResponse GetResponseByPost(HttpPostArguments httpPostArguments, OnGetRequestStream GetRequestStreamHandler)
        {
            CopyHeadersTo(_webRequest);
            byte[] bytes = null;
            string boundary = null;
            if (!httpPostArguments.IsMultipart)
            {
                if (AutoEncoding)
                    httpPostArguments.Encoding = GuessEncoding(true);
                bytes = httpPostArguments.BuildByteArray();
                _webRequest.ContentLength = bytes.Length;
                _webRequest.ContentType = UploadValuesContentType;
            }
            else
            {
                boundary = httpPostArguments.Boundary == null ? DefaultBoundaryPrefix + GetBoundary() : httpPostArguments.Boundary;
                _webRequest.ContentType = DefaultMultipartContentType + boundary;


            }

            using (Stream stream = _webRequest.GetRequestStream())
            {
                bool handled = false;
                if (null != GetRequestStreamHandler)
                    GetRequestStreamHandler(stream, ref handled);
                if (!handled)
                {
                    if (!httpPostArguments.IsMultipart)
                        stream.Write(bytes, 0, bytes.Length);
                    else
                        BuildMultipartBytes(stream, httpPostArguments, boundary);
                }
            }
            return (HttpWebResponse)_webRequest.GetResponse();
        }

        private byte[] GetResponseBytes(OnGetResponseStream GetResponseStreamHandler)
        {
            byte[] bytes;
            using(Stream stream  = _webResponse.GetResponseStream())
            {
                if (null != GetResponseStreamHandler)
                    bytes = GetResponseStreamHandler(stream);
                else
                {
                    bytes = ResponseAsBytes(stream, null);
                }
                
            }
            return bytes;
        }

        private byte[] ResponseAsBytes(Stream stream, uint? maxBytesLength)
        {
            long length = _webResponse.ContentLength;
            bool unknownsize = false;

            if (length == -1)
            {
                unknownsize = true;
                length = DefaultDownloadBufferLength;
            }
            if(maxBytesLength.HasValue)
            {
                length = Math.Min(maxBytesLength.Value, length);
            }

            byte[] buffer = new byte[(int)length];
            int nread;
            int offset = 0;

            do
            {
                nread = stream.Read(buffer, offset, (int)length - offset);
                offset += nread;
                if (unknownsize && offset == length)
                {
                    length += DefaultDownloadBufferLength;
                    if (maxBytesLength.HasValue)
                    {
                        if (offset == maxBytesLength)
                            break;
                        length = Math.Min(maxBytesLength.Value, length);
                    }
                    byte[] newbuf = new byte[(int)length];

                    Buffer.BlockCopy(buffer, 0, newbuf, 0, offset);

                    buffer = newbuf;
                }
            } while (nread != 0);
            if (unknownsize)
            {
                byte[] newbuf = new byte[offset];
                Buffer.BlockCopy(buffer, 0, newbuf, 0, offset);
                buffer = newbuf;
            }
            return buffer;
        }


        private Encoding GuessEncoding(bool request)
        {
            try
            {
                string contentType;
                if ((contentType = request ? _webRequest.ContentType : _webResponse.ContentType) == null)
                {
                    return this.Encoding;
                }
                contentType = contentType.ToLower(CultureInfo.InvariantCulture);
                string[] parsedList = contentType.Split(new char[] { ';', '=', ' ' });
                bool nextItem = false;
                foreach (string item in parsedList)
                {
                    if (item == "charset")
                    {
                        nextItem = true;
                    }
                    else if (nextItem)
                    {
                        return Encoding.GetEncoding(item);
                    }
                }
            }
            catch (Exception e)
            {
                if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException)
                {
                    throw;
                }
            }
            return this.Encoding;
        }


        private static void BuildMultipartBytes(Stream netstream, HttpPostArguments data, string boundary)
        {
            if (data.Count == 0)
                throw new NotSupportedException("Check Data Count");
            string part0 = string.Concat("--", boundary, "\r\n");
            foreach (string key in data.Keys)
            {
                string part1;
                string part2;

                part1 = String.Format("{0}{1}{2}{1}", DefaultContentDisposition, "\"", key);
                if (!data.Ismultipart(key))
                {
                    part1 = string.Concat(part1, "\r\n\r\n") ;
                    part2 = string.Concat(data[key], "\r\n");
                    AddStringToStream(netstream, string.Concat(part0, part1, part2));
                }
                else
                {
                    if (data[key] is String[])
                    {
                        FileInfo info;
                        if (((String[])data[key]).Length == 1)
                        {
                            string path = ((String[])data[key])[0];
                            info = new FileInfo(path);
                            part1 = String.Format("{0}; filename={1}{2}{1}\r\n", part1, "\"", path);
                            part2 = String.Format("Content-Type: {0}\r\n\r\n", GetFileContentType(info.Extension));
                            AddStringToStream(netstream, string.Concat(part0, part1, part2));
                            AddBytesToStream(netstream, path);
                            AddStringToStream(netstream, "\r\n");
                        }
                        else
                        {
                            string mixedboundary = DefaultBoundaryPrefix + GetBoundary();
                            part1 += "\r\n";
                            part2 = string.Concat(DefaultMixedContentType, mixedboundary, "\r\n\r\n");
                            AddStringToStream(netstream, string.Concat(part0, part1, part2));
                            foreach (string path in (string[])data[key])
                            {
                                info = new FileInfo(path);
                                part0 = string.Concat("--", mixedboundary, "\r\n");
                                part1 = String.Format("{0}{1}{2}{1}", DefaultMixContentDisposition, "\"", path);
                                part2 = String.Format("Content-Type: {0}\r\n\r\n", GetFileContentType(info.Extension));
                                AddStringToStream(netstream, string.Concat(part0, part1, part2));
                                AddBytesToStream(netstream, path);
                                AddStringToStream(netstream, "\r\n");
                            }
                            AddStringToStream(netstream, string.Concat("--", mixedboundary, "--", "\r\n"));
                        }
                    }
                }
            }
            AddStringToStream(netstream, string.Concat("--",boundary, "--", "\r\n"));
        }

        private static void AddStringToStream(Stream stream, string str)
        {
            byte[] PostData = Encoding.GetEncoding("ISO-8859-1").GetBytes(str);
            stream.Write(PostData, 0, PostData.Length);
        }

        private static void AddBytesToStream(Stream stream, string filePath)
        {
            FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            byte[] bytes = new byte[DefaultCopyBufferLength];
            while (true)
            {
                int receiving;
                receiving = fileStream.Read(bytes, 0, DefaultCopyBufferLength);
                if (receiving > 0)
                    stream.Write(bytes, 0, receiving);
                else
                    break;
            }
            fileStream.Close();
        }


        private static string GetBoundary()
        {
            return DateTime.Now.Ticks.ToString("x").Substring(4);
        }

        private static string GetFileContentType(string fileExtension)
        {
            RegistryKey oRegKey = null;
            RegistryKey oFileExtKey = null;
            string sFileContentType = null;
            try
            {
                oRegKey = Registry.ClassesRoot;
                oFileExtKey = oRegKey.OpenSubKey(fileExtension);
                Object tp;
                if (oFileExtKey != null)
                    sFileContentType = (tp = oFileExtKey.GetValue("Content Type")) == null ? null : tp.ToString();
                if (oFileExtKey == null || sFileContentType == null)
                {
                    oFileExtKey = oRegKey.OpenSubKey("MIME\\Database\\Content Type");
                    if (oFileExtKey != null)
                    {
                        foreach (string type in oFileExtKey.GetSubKeyNames())
                        {

                            if ((tp = oFileExtKey.OpenSubKey(type).GetValue("Extension")) != null && tp.ToString().Equals(fileExtension.ToLower()))
                            {
                                sFileContentType = type;
                                break;
                            }
                        }
                    }
                }
            }
            catch { }
            finally
            {
                if (oFileExtKey != null)
                    oFileExtKey.Close();
                if (oRegKey != null)
                    oRegKey.Close();
                oFileExtKey = null;
                oRegKey = null;

            }
            if (sFileContentType == null)
                sFileContentType = DefaultMultipartFileContentType;
            return sFileContentType;
        } 


        private void CopyHeadersTo(HttpWebRequest request)
        {
                string accept = ClientHeads[HttpKnownHeaderNames.Accept];
                string connection = ClientHeads[HttpKnownHeaderNames.Connection];
                string contentType = ClientHeads[HttpKnownHeaderNames.ContentType];
                string expect = ClientHeads[HttpKnownHeaderNames.Expect];
                string referrer = ClientHeads[HttpKnownHeaderNames.Referer];
                string userAgent = ClientHeads[HttpKnownHeaderNames.UserAgent];

                ClientHeads.Remove(HttpKnownHeaderNames.Accept);
                ClientHeads.Remove(HttpKnownHeaderNames.Connection);
                ClientHeads.Remove(HttpKnownHeaderNames.ContentType);
                ClientHeads.Remove(HttpKnownHeaderNames.Expect);
                ClientHeads.Remove(HttpKnownHeaderNames.Referer);
                ClientHeads.Remove(HttpKnownHeaderNames.UserAgent);
                request.Headers = ClientHeads;
                if ((accept != null) && (accept.Length > 0))
                {
                    request.Accept = accept;
                }
                if ((connection != null) && (connection.Length > 0))
                {
                    request.Connection = connection;
                }
                if ((contentType != null) && (contentType.Length > 0))
                {
                    request.ContentType = contentType;
                }
                if ((expect != null) && (expect.Length > 0))
                {
                    request.Expect = expect;
                }
                if ((referrer != null) && (referrer.Length > 0))
                {
                    request.Referer = referrer;
                }
                if ((userAgent != null) && (userAgent.Length > 0))
                {
                    request.UserAgent = userAgent;
                }
        }


        public void AbortRequest()
        {
            if (null != _webRequest && IsBusy)
            {
                try
                {
                    _webRequest.Abort();
                }
                catch (Exception ex)
                {
                    _lastException = ex;
                }
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            AbortRequest();
            _webRequest = null;
            if (null != _webResponse)
                ((IDisposable)_webResponse).Dispose();
            _webResponse = null;
        }

        #endregion

        #region IHttpMethod Members


        public bool AutoEncoding
        {
            get { return _autoEncoding; }
            set { _autoEncoding = value; }
        }

        public string GetResponseString(Uri url, NameValueCollection requestParam)
        {
            return GetResponseString(GetUri(url, requestParam), (NameValueCollection)null, null);
        }

        public string GetResponseString(Uri fullUrl)
        {
            return GetResponseString(GetUri(fullUrl, null), (uint?)null);
        }

        public string GetResponseString(Uri url, HttpPostArguments arguments)
        {
            return GetResponseString(GetUri(url, null), arguments, null);
        }

        public byte[] DownloadData(Uri url)
        {
            throw new NotImplementedException();
        }

        public byte[] UploadData(Uri url, byte[] data)
        {
            throw new NotImplementedException();
        }


        public string GetResponseString(string url, NameValueCollection requestParam)
        {
            return GetResponseString(GetUri(url, requestParam));
        }

        public string GetResponseString(string fullUrl)
        {
            return GetResponseString(GetUri(fullUrl, null));
        }

        public string GetResponseString(string url, HttpPostArguments arguments)
        {
            return GetResponseString(GetUri(url, null), arguments);
        }

        public byte[] DownloadData(string url)
        {
            throw new NotImplementedException();
        }

        public byte[] UploadData(string url, byte[] data)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IhttpSpecialMethod Members

        public string GetResponseString(string url, NameValueCollection requestParam, uint? maxBytesLength)
        {
            return GetResponseString(GetUri(url, requestParam), maxBytesLength);
        }

        public string GetResponseString(string fullUrl, uint? maxBytesLength)
        {
            return GetResponseString(GetUri(fullUrl, null), maxBytesLength);
        }

        public string GetResponseString(string url, HttpPostArguments arguments, uint? maxBytesLength)
        {
            return GetResponseString(GetUri(url, null), arguments, maxBytesLength);
        }

        public byte[] DownloadData(string url, uint? bytesLength)
        {
            throw new NotImplementedException();
        }

        public byte[] UploadData(string url, byte[] data, uint? maxBytesLength)
        {
            throw new NotImplementedException();
        }

        public string GetResponseString(Uri url, NameValueCollection requestParam, uint? maxBytesLength)
        {
            return GetResponseString(GetUri(url, requestParam), maxBytesLength);
        }

        public string GetResponseString(Uri fullUrl, uint? maxBytesLength)
        {
            return GetResponseString(GetUri(fullUrl, null), maxBytesLength);
            
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public string GetResponseString(Uri url, HttpPostArguments arguments, uint? maxBytesLength)
        {
            OnGetResponseStream onGetResponseStream = maxBytesLength.HasValue ?
                                        delegate(Stream netStream)
                                        {
                                            return ResponseAsBytes(netStream,
                                                maxBytesLength);
                                        }
                                  : (OnGetResponseStream)null;
            byte[] bytes = GetResponseBytes(url, arguments, null, onGetResponseStream, true);
            Encoding = AutoEncoding ? GuessEncoding(false) : Encoding;
            return bytes == null ? null : Encoding.GetString(bytes);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public byte[] DownloadData(Uri url, uint? maxBytesLength)
        {
            throw new NotImplementedException();
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public byte[] UploadData(Uri url, byte[] data, uint? maxBytesLength)
        {
            throw new NotImplementedException();
        }

        #endregion



        public static Uri GetUri(string path, NameValueCollection requestParam)
        {

            Uri uri;
            if (!Uri.TryCreate(path, UriKind.Absolute, out uri))
                return new UriBuilder(Uri.UriSchemeHttp, path).Uri;

            return GetUri(uri, requestParam);
        }

        public static Uri GetUri(Uri address, NameValueCollection requestParam)
        {
            if (address == null)
                throw new ArgumentNullException("address");

            Uri uri = address;

            Debug.Assert(address.IsAbsoluteUri);

            if ((uri.Query == null || uri.Query == string.Empty) && requestParam != null)
            {

                StringBuilder sb = new StringBuilder();
                string delimiter = String.Empty;

                for (int i = 0; i < requestParam.Count; ++i)
                {
                    sb.Append(delimiter
                              + requestParam.AllKeys[i]
                              + "="
                              + requestParam[i]
                              );
                    delimiter = "&";
                }

                UriBuilder ub = new UriBuilder(uri);

                ub.Query = sb.ToString();
                uri = ub.Uri;
            }
            return uri;
        }

    }


    public static class HttpKnownHeaderNames
    {
        public const string CacheControl = "Cache-Control";
        public const string Connection = "Connection";
        public const string Date = "Date";
        public const string KeepAlive = "Keep-Alive";
        public const string Pragma = "Pragma";
        public const string ProxyConnection = "Proxy-Connection";
        public const string Trailer = "Trailer";
        public const string TransferEncoding = "Transfer-Encoding";
        public const string Upgrade = "Upgrade";
        public const string Via = "Via";
        public const string Warning = "Warning";
        public const string ContentLength = "Content-Length";
        public const string ContentType = "Content-Type";
        public const string ContentEncoding = "Content-Encoding";
        public const string ContentLanguage = "Content-Language";
        public const string ContentLocation = "Content-Location";
        public const string ContentRange = "Content-Range";
        public const string Expires = "Expires";
        public const string LastModified = "Last-Modified";
        public const string Age = "Age";
        public const string Location = "Location";
        public const string ProxyAuthenticate = "Proxy-Authenticate";
        public const string RetryAfter = "Retry-After";
        public const string Server = "Server";
        public const string SetCookie = "Set-Cookie";
        public const string SetCookie2 = "Set-Cookie2";
        public const string Vary = "Vary";
        public const string WWWAuthenticate = "WWW-Authenticate";
        public const string Accept = "Accept";
        public const string AcceptCharset = "Accept-Charset";
        public const string AcceptEncoding = "Accept-Encoding";
        public const string AcceptLanguage = "Accept-Language";
        public const string Authorization = "Authorization";
        public const string Cookie = "Cookie";
        public const string Cookie2 = "Cookie2";
        public const string Expect = "Expect";
        public const string From = "From";
        public const string Host = "Host";
        public const string IfMatch = "If-Match";
        public const string IfModifiedSince = "If-Modified-Since";
        public const string IfNoneMatch = "If-None-Match";
        public const string IfRange = "If-Range";
        public const string IfUnmodifiedSince = "If-Unmodified-Since";
        public const string MaxForwards = "Max-Forwards";
        public const string ProxyAuthorization = "Proxy-Authorization";
        public const string Referer = "Referer";
        public const string Range = "Range";
        public const string UserAgent = "User-Agent";
        public const string ContentMD5 = "Content-MD5";
        public const string ETag = "ETag";
        public const string TE = "TE";
        public const string Allow = "Allow";
        public const string AcceptRanges = "Accept-Ranges";
        public const string P3P = "P3P";
        public const string XPoweredBy = "X-Powered-By";
        public const string XAspNetVersion = "X-AspNet-Version";
    }

}
